<!DOCTYPE HTML>
<html lang="zh-Hans" class="sidebar-visible no-js light">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>SeaORM：要做Rust版本的ActiveRecord - Rust 优秀博文</title>


        <!-- Custom HTML head -->
        
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff" />

        <link rel="icon" href="../../favicon.svg">
        <link rel="shortcut icon" href="../../favicon.png">
        <link rel="stylesheet" href="../../css/variables.css">
        <link rel="stylesheet" href="../../css/general.css">
        <link rel="stylesheet" href="../../css/chrome.css">
        <link rel="stylesheet" href="../../css/print.css" media="print">

        <!-- Fonts -->
        <link rel="stylesheet" href="../../FontAwesome/css/font-awesome.css">
        <link rel="stylesheet" href="../../fonts/fonts.css">

        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="../../highlight.css">
        <link rel="stylesheet" href="../../tomorrow-night.css">
        <link rel="stylesheet" href="../../ayu-highlight.css">

        <!-- Custom theme stylesheets -->

    </head>
    <body>
        <!-- Provide site root to javascript -->
        <script type="text/javascript">
            var path_to_root = "../../";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script type="text/javascript">
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script type="text/javascript">
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('no-js')
            html.classList.remove('light')
            html.classList.add(theme);
            html.classList.add('js');
        </script>

        <!-- Hide / unhide sidebar before it is displayed -->
        <script type="text/javascript">
            var html = document.querySelector('html');
            var sidebar = 'hidden';
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            }
            html.classList.remove('sidebar-visible');
            html.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">1.</strong> 类型系统</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../01_类型系统/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow.html"><strong aria-hidden="true">1.1.</strong> Rust Concept Clarification Deref vs AsRef vs Borrow vs Cow</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow/Deref_AsRef_Borrow_Cow释义.html"><strong aria-hidden="true">1.2.</strong> Deref AsRef Borrow Cow 释义</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然/Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然.html"><strong aria-hidden="true">1.3.</strong> Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust的Cow类型有什么用？详解Cow及其用途/Rust的Cow类型有什么用？详解Cow及其用途.html"><strong aria-hidden="true">1.4.</strong> Rust的Cow类型有什么用？详解Cow及其用途</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/判别Fn、FnMut、FnOnce的标准/判别Fn、FnMut、FnOnce的标准.html"><strong aria-hidden="true">1.5.</strong> 判别Fn、FnMut、FnOnce的标准</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/一行代码告诉你内部可变性的真相(UnsafeCell)/一行代码告诉你内部可变性的真相(UnsafeCell).html"><strong aria-hidden="true">1.6.</strong> 一行代码告诉你内部可变性的真相(UnsafeCell)</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Tour_of_Rust's_Standard_Library_Traits/Tour_of_Rust's_Standard_Library_Traits.html"><strong aria-hidden="true">1.7.</strong> Tour of Rust's Standard Library Traits</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/逆变、协变与子类型，以及Rust/逆变、协变与子类型，以及Rust.html"><strong aria-hidden="true">1.8.</strong> 逆变、协变与子类型，以及Rust</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust自引用结构、Pin与Unpin/Rust自引用结构、Pin与Unpin.html"><strong aria-hidden="true">1.9.</strong> Rust自引用结构、Pin与Unpin</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/为什么Rust需要Pin,Unpin/为什么Rust需要Pin,Unpin.html"><strong aria-hidden="true">1.10.</strong> 译：为什么 Rust 需要 Pin, Unpin ？</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/定海神针Pin和Unpin/定海神针Pin和Unpin.html"><strong aria-hidden="true">1.11.</strong> 译：定海神针 Pin 和 Unpin</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/sizedness-in-rust/sizedness-in-rust.html"><strong aria-hidden="true">1.12.</strong> Sizedness in Rust</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust生命周期集大成者PhantomData〈T〉/Rust生命周期集大成者PhantomData〈T〉.html"><strong aria-hidden="true">1.13.</strong> Rust生命周期集大成者 PhantomData&lt;T&gt;</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用Rust做类型体操_Part_0.html"><strong aria-hidden="true">1.14.</strong> 数据库表达式执行的黑魔法：用Rust做类型体操 Part 0</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：GAT实现引用类型关联_Part_1.html"><strong aria-hidden="true">1.15.</strong> 数据库表达式执行的黑魔法：GAT实现引用类型关联 Part 1</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用HRTB写bound_Part_2.html"><strong aria-hidden="true">1.16.</strong> 数据库表达式执行的黑魔法：用HRTB写bound Part 2</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用Rust做类型体操之用宏展开重复代码_Part_3_&_4.html"><strong aria-hidden="true">1.17.</strong> 数据库表达式执行的黑魔法：用Rust做类型体操之用宏展开重复代码 Part 3 & 4</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：与Rust编译器斗智斗勇之表达式向量化_Part_5_&_6.html"><strong aria-hidden="true">1.18.</strong> 数据库表达式执行的黑魔法：与Rust编译器斗智斗勇之表达式向量化 Part 5 & 6</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：在Rust中用宏关联逻辑类型和实际类型_Part_7.html"><strong aria-hidden="true">1.19.</strong> 数据库表达式执行的黑魔法：在Rust中用宏关联逻辑类型和实际类型 Part 7</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">2.</strong> 生命周期</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../02_生命周期/Rust中的生命周期——从StrSplit实例说开去/Rust中的生命周期——从StrSplit实例说开去.html"><strong aria-hidden="true">2.1.</strong> Rust中的生命周期——从StrSplit实例说开去</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/与ChatGPT深度对话来学Rust生命周期/与ChatGPT深度对话来学Rust生命周期.html"><strong aria-hidden="true">2.2.</strong> 与ChatGPT深度对话来学Rust生命周期</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——early_bound与late_bound（1）/进击的Rust生命周期——early_bound与late_bound（1）.html"><strong aria-hidden="true">2.3.</strong> 进击的Rust生命周期——early_bound与late_bound（1）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——early_bound与late_bound（2）/进击的Rust生命周期——early_bound与late_bound（2）.html"><strong aria-hidden="true">2.4.</strong> 进击的Rust生命周期——early_bound与late_bound（2）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——一力降十会的MIR（1）/进击的Rust生命周期——一力降十会的MIR（1）.html"><strong aria-hidden="true">2.5.</strong> 进击的Rust生命周期——一力降十会的MIR（1）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——一力降十会的MIR（2）/进击的Rust生命周期——一力降十会的MIR（2）.html"><strong aria-hidden="true">2.6.</strong> 进击的Rust生命周期——一力降十会的MIR（2）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/Common_Rust_Lifetime_Misconceptions/Common_Rust_Lifetime_Misconceptions.html"><strong aria-hidden="true">2.7.</strong> Common Rust Lifetime Misconceptions</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/Rust生命周期常见误区/Rust生命周期常见误区.html"><strong aria-hidden="true">2.8.</strong> 译：Rust生命周期常见误区</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">3.</strong> 无畏并发</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../05_无畏并发/简单写个Rust无锁队列/简单写个Rust无锁队列.html"><strong aria-hidden="true">3.1.</strong> 简单写个Rust无锁队列</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——混合自旋锁/进击的Rust多线程——混合自旋锁.html"><strong aria-hidden="true">3.2.</strong> 进击的Rust多线程——混合自旋锁</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/An_unsafe_tour_of_Rust's_Send_and_Sync/An_unsafe_tour_of_Rust's_Send_and_Sync.html"><strong aria-hidden="true">3.3.</strong> An unsafe tour of Rust's Send and Sync</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——Send与Sync/进击的Rust多线程——Send与Sync.html"><strong aria-hidden="true">3.4.</strong> 进击的Rust多线程——Send与Sync</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——离经叛道的PhantomData/进击的Rust多线程——离经叛道的PhantomData.html"><strong aria-hidden="true">3.5.</strong> 进击的Rust多线程——离经叛道的PhantomData</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust_Async_Pin概念解析/Rust_Async_Pin概念解析.html"><strong aria-hidden="true">3.6.</strong> Rust Async: Pin概念解析</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust和C++的并发库对比/Rust和C++的并发库对比.html"><strong aria-hidden="true">3.7.</strong> 译：Rust 和 C++ 的并发库对比</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust原子类型和内存排序/Rust原子类型和内存排序.html"><strong aria-hidden="true">3.8.</strong> Rust原子类型和内存排序</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">4.</strong> 网络编程</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../06_网络编程/从编解码层面理解WebSocket_手写一个WebSocket/从编解码层面理解WebSocket_手写一个WebSocket.html"><strong aria-hidden="true">4.1.</strong> 从编解码层面理解WebSocket 手写一 个WebSocket</a></li><li class="chapter-item expanded "><a href="../../06_网络编程/透过Rust探索系统的本原：网络篇/透过Rust探索系统的本原：网络篇.html"><strong aria-hidden="true">4.2.</strong> 透过Rust探索系统的本原：网络篇</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">5.</strong> 轮子系列</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../07_轮子系列/700行Rust写一个内存分配器/700行Rust写一个内存分配器.html"><strong aria-hidden="true">5.1.</strong> 700行Rust写一个内存分配器</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/Rust：网络库的实现思路/Rust：网络库的实现思路.html"><strong aria-hidden="true">5.2.</strong> Rust：网络库的实现思路</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/Rust异步运行时基础部件/Rust异步运行时基础部件.html"><strong aria-hidden="true">5.3.</strong> Rust异步运行时基础部件</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（1）.html"><strong aria-hidden="true">5.4.</strong> 使用Rust+epoll编写异步IO框架（1）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（2）.html"><strong aria-hidden="true">5.5.</strong> 使用Rust+epoll编写异步IO框架（2）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（3）.html"><strong aria-hidden="true">5.6.</strong> 使用Rust+epoll编写异步IO框架（3）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day1.html"><strong aria-hidden="true">5.7.</strong> 用rust从零开发一套web框架：day1</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day2.html"><strong aria-hidden="true">5.8.</strong> 用rust从零开发一套web框架：day2</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day3.html"><strong aria-hidden="true">5.9.</strong> 用rust从零开发一套web框架：day3</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day4.html"><strong aria-hidden="true">5.10.</strong> 用rust从零开发一套web框架：day4</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day5.html"><strong aria-hidden="true">5.11.</strong> 用rust从零开发一套web框架：day5</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">6.</strong> 奇技淫巧</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Copy-On-Write是不是优化？/Copy-On-Write是不是优化？.html"><strong aria-hidden="true">6.1.</strong> 译：Copy-On-Write是不是优化？</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/揭秘神奇的Rust_Axum风格的函数实现/揭秘神奇的Rust_Axum风格的函数实现.html"><strong aria-hidden="true">6.2.</strong> 译：揭秘神奇的 Rust Axum 风格的函数实现</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/“变长参数”函数与回调/“变长参数”函数与回调.html"><strong aria-hidden="true">6.3.</strong> “变长参数”函数与回调</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Rust字符串格式化的幕后：format_args!()/Rust字符串格式化的幕后：format_args!().html"><strong aria-hidden="true">6.4.</strong> 译：Rust字符串格式化的幕后：format_args!()</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/给Rust带来一点C++特产/给Rust带来一点C++特产.html"><strong aria-hidden="true">6.5.</strong> 给Rust带来一点C++特产</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/一步步实现_Rust_Bevy_ECS_的_System_简化版本/一步步实现_Rust_Bevy_ECS_的_System_简化版本.html"><strong aria-hidden="true">6.6.</strong> 一步步实现 Rust Bevy ECS 的 System 简化版本</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Exploring_Design_Patterns_in_Rust_with_Algorithmic_Trading_Examples/Exploring_Design_Patterns_in_Rust_with_Algorithmic_Trading_Examples.html"><strong aria-hidden="true">6.7.</strong> Exploring Design Patterns in Rust with Algorithmic Trading Examples</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">7.</strong> 源码分析</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：bytes源码分析/Rust并发：bytes源码分析.html"><strong aria-hidden="true">7.1.</strong> Rust并发：bytes源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：标准库Arc源码分析/Rust并发：标准库Arc源码分析.html"><strong aria-hidden="true">7.2.</strong> Rust并发：标准库Arc源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：标准库sync_Once源码分析/Rust并发：标准库sync_Once源码分析.html"><strong aria-hidden="true">7.3.</strong> Rust并发：标准库sync::Once源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust源码阅读：引用计数Rc/Rust源码阅读：引用计数Rc.html"><strong aria-hidden="true">7.4.</strong> Rust源码阅读：引用计数Rc</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust源码阅读：Cell、RefCell与内部可变性/Rust源码阅读：Cell、RefCell与内部可变性.html"><strong aria-hidden="true">7.5.</strong> Rust源码阅读： Cell、RefCell与内部可变性</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/关于_Rust_的_UnsafeCell、Cell_与_RefCell/关于_Rust_的_UnsafeCell、Cell_与_RefCell.html"><strong aria-hidden="true">7.6.</strong> 关于 Rust 的 UnsafeCell、Cell 与 RefCell</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust_Async_async-stream源码分析/Rust_Async_async-stream源码分析.html"><strong aria-hidden="true">7.7.</strong> Rust Async: async-stream源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/走进Tokio的异步世界/走进Tokio的异步世界.html"><strong aria-hidden="true">7.8.</strong> 走进 Tokio 的异步世界</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/tokio.rs_runtime的实现/tokio.rs_runtime的实现.html"><strong aria-hidden="true">7.9.</strong> tokio.rs runtime 的实现</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_internals/Tokio_internals.html"><strong aria-hidden="true">7.10.</strong> Tokio internals</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_internals/译文：Tokio內部机制.html"><strong aria-hidden="true">7.11.</strong> 译：Tokio 内部机制</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust_Axum_HTTP_框架的架构分析/Rust_Axum_HTTP_框架的架构分析.html"><strong aria-hidden="true">7.12.</strong> Rust Axum HTTP 框架的架构分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/安利一个Rust_Game_Engine：Bevy--ECS部分/安利一个Rust_Game_Engine：Bevy--ECS部分.html"><strong aria-hidden="true">7.13.</strong> 安利一个Rust Game Engine：Bevy--ECS部分</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_解析之任务调度/Tokio_解析之任务调度.html"><strong aria-hidden="true">7.14.</strong> Tokio 解析之任务调度</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">8.</strong> 生态观察</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../11_生态观察/Rust_web_frameworks_have_subpar_error_reporting/Rust_web_frameworks_have_subpar_error_reporting.html"><strong aria-hidden="true">8.1.</strong> Rust web frameworks have subpar error reporting</a></li><li class="chapter-item expanded "><a href="../../11_生态观察/SeaORM：要做Rust版本的ActiveRecord/SeaORM：要做Rust版本的ActiveRecord.html" class="active"><strong aria-hidden="true">8.2.</strong> SeaORM：要做Rust版本的ActiveRecord</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">9.</strong> 死灵终极</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_总述.html"><strong aria-hidden="true">9.1.</strong> 译：Learn Rust the Dangerous Way 总述</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_0.html"><strong aria-hidden="true">9.2.</strong> 译：Learn Rust the Dangerous Way 0</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_1.html"><strong aria-hidden="true">9.3.</strong> 译：Learn Rust the Dangerous Way 1</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_2.html"><strong aria-hidden="true">9.4.</strong> 译：Learn Rust the Dangerous Way 2</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_3.html"><strong aria-hidden="true">9.5.</strong> 译：Learn Rust the Dangerous Way 3</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_4.html"><strong aria-hidden="true">9.6.</strong> 译：Learn Rust the Dangerous Way 4</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_5.html"><strong aria-hidden="true">9.7.</strong> 译：Learn Rust the Dangerous Way 5</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（一）.html"><strong aria-hidden="true">9.8.</strong> Unsafe Rust 随堂小测（一）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（二）.html"><strong aria-hidden="true">9.9.</strong> Unsafe Rust 随堂小测（二）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（三）.html"><strong aria-hidden="true">9.10.</strong> Unsafe Rust 随堂小测（三）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测参考答案.html"><strong aria-hidden="true">9.11.</strong> Unsafe Rust 随堂小测参考答案</a></li></ol></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
        </nav>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky bordered">
                    <div class="left-buttons">
                        <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </button>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                    </div>

                    <h1 class="menu-title">Rust 优秀博文</h1>

                    <div class="right-buttons">
                        <a href="../../print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>

                    </div>
                </div>

                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>

                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script type="text/javascript">
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h1 id="rust-web-生态观察-seaorm-要做-rust-版本的-activerecord"><a class="header" href="#rust-web-生态观察-seaorm-要做-rust-版本的-activerecord">Rust Web 生态观察｜ SeaORM ：要做 Rust 版本的 ActiveRecord</a></h1>
<p>作者：<a href="https://www.zhihu.com/people/blackanger">张汉东</a></p>
<p>原载：<a href="https://zhuanlan.zhihu.com/p/417431284">https://zhuanlan.zhihu.com/p/417431284</a></p>
<h2 id="引子"><a class="header" href="#引子"><strong>引子</strong></a></h2>
<p>有些人说用 Rust 进行 Web 开发 是杀鸡用牛刀，这种观点其实是对「系统级语言」的刻板印象造成的。无论从性能、工程架构还是开发效率，Rust 其实都很出色，目前就是需要一套比较成熟的框架。无论如何，Rust 在 Web 开发领域的生态在逐步成型。</p>
<p>注意：这里的 Web 是指更加广义的 Web ，不仅仅是 CRUD，还包括 网络服务、云原生服务端、 WebAssembly 、嵌入式物联网、区块链等等。</p>
<p>这也促使我想写《Rust Web 生态观察》系列文章，时间精力有限，不定时更新。希望能给大家提供一个视角，来客观地 了解 Rust 在 Web 开发领域的发展。</p>
<h2 id="rust-orm-生态"><a class="header" href="#rust-orm-生态"><strong>Rust ORM 生态</strong></a></h2>
<p>Rust ORM 生态中，最早的 ORM 是 <a href="https://link.zhihu.com/?target=https%3A//diesel.rs/">Diesel</a>。Diesel 的作者 <code>sgrif</code> 曾经也是 ActiveRecord （知名 Web 框架 Ruby on Rails 内置的 ORM ）的核心贡献者。Diesel ORM 的设计也是 sgrif 总结了 AR 中的经验教训。Diesel 是一个优秀的 ORM 框架，但是它并不支持异步。并且，Diesel 并不是 ActiveRecord 的 Rust 复刻版。</p>
<blockquote>
<p>“ Active Record ，是一种领域模型模式，特点是一个模型类对应关系型数据库中的一个表，而模型类的一个实例对应表中的一行记录。它并不是 Ruby on Rails 首创，而是由 Martin Fowler 的《企业应用架构模式》一书中提出的。
Rails 的 Active Record ORM 框架，和 Rails 框架一样，遵循的是「约定大于配置」的惯例。比如 ：</p>
<p>\1. User 模型，对应的是 users 表。遵循单复数的约定。
\2. 默认会以 <code>id</code>字段为主键。而以 <code>_id</code>后缀的字段作为外键。
\3. 自动生成 <code>find_by_id</code> 之类的查询方法。
\4. 以 <code>created_at</code> 和 <code>updated_at</code> 在创建和更新记录的时候，自动设置时间戳。
\5. <code>(table_name)_count</code> ，保存关联对象的数量。
\6. 其他。</p>
</blockquote>
<p>ORM 有两种模式： <strong>Active Record 与 Data Mapper</strong></p>
<ul>
<li>ActiveRecord : 一个对象既包含数据又包含行为。这些数据大部分是持久性的，需要存储在数据库中。Active Record使用最明显的方法，将数据访问逻辑放在域对象中。这样，所有人都知道如何在数据库中读取和写入数据。</li>
<li>DataMapper: 与<code>Active Record</code>不一样的地方在于它增加了一个映射器，把持久化对象的数据跟行为分开了。它的关键地方在于数据模型遵循了单一职责原则。DataMapper 适合更加复杂的层次结构。</li>
</ul>
<p>随着 Rust 异步生态的发展，ORM 异步支持的需求也逐渐增多。</p>
<p>之后，<code>sqlx</code> 出现了。Go 语言生态中也有同名的数据库包，不确定 Rust 这个 <code>sqlx</code> 的命名是否参考它。</p>
<p><code>sqlx</code>并不是一个 ORM 框架，它没有像<code>Diesel</code>这类支持orm框架的 DSL ，用户可以自己编写<code>sql</code>语句，将查询结果按列取出或映射到<code>struct</code>上。它的一些特点：</p>
<ul>
<li>支持 <code>async-std</code> 和<code>tokio</code></li>
<li>编译时查询检查（可选）</li>
<li>内置连接池</li>
<li>支持 <code>postgresql</code> 、<code>mysql/maridb</code>、<code>sqlite</code></li>
<li>纯 <code>Rust</code>实现<code>mysql</code>和<code>postgresql</code> 访问驱动程序（<code>sqlite</code>使用了<code>libsqlite3 C</code> 库）</li>
<li>支持 TLS</li>
<li>嵌套事务</li>
</ul>
<p>sqlx 使用起来相对比较“原始”，直接操作 SQL 语句，没有 ORM 不太方便。</p>
<p>国内 Rust 社区小伙伴 <a href="https://link.zhihu.com/?target=https%3A//github.com/zhuxiujia">@zhuxiujia</a> 也实现了一个异步 ORM 框架 <a href="https://link.zhihu.com/?target=https%3A//github.com/rbatis/rbatis">rbatis</a>。Rbatis 也是基于 sqlx 实现的，它的灵感来自于 Java 的 ORM 框架 Mybatis。Rbatis 提供了一些内置插件，可以针对一些常用场景增加开发效率。</p>
<p>而我们今天要看的主角是<code>sea-orm</code> ，它是基于 <code>sqlx</code> 实现的 ORM 框架，号称要实现 Rust 版本 的 <a href="https://link.zhihu.com/?target=https%3A//guides.rubyonrails.org/active_record_basics.html">ActiveRecord</a> 。</p>
<h2 id="seaorm--要做-rust-版本的-active-record"><a class="header" href="#seaorm--要做-rust-版本的-active-record"><strong>SeaORM : 要做 Rust 版本的 Active Record</strong></a></h2>
<p>既然 <code>sea-orm</code> 喊出这样的口号，那它的架构设计肯定和 Active Record 是有点关系吧？让我们先从它的 API 开始探索。</p>
<h3 id="seaorm-示例"><a class="header" href="#seaorm-示例"><strong>SeaORM 示例</strong></a></h3>
<p>从它的 example 项目中可以看到如下使用示例：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// https://github.com/SeaQL/sea-orm/blob/master/examples/rocket_example/src/main.rs
// 只摘录关键代码

mod post;
pub use post::Entity as Post;

const DEFAULT_POSTS_PER_PAGE: usize = 5;

// 使用 Rocket web 框架的一个 endpoint api
#[post(&quot;/&quot;, data = &quot;&lt;post_form&gt;&quot;)]
async fn create(conn: Connection&lt;Db&gt;, post_form: Form&lt;post::Model&gt;) -&gt; Flash&lt;Redirect&gt; {
    let form = post_form.into_inner();

    // 注意 ActiveModel ，这个在 Rails 的 ActiveRecord 中也有同名组件
    post::ActiveModel {
        title: Set(form.title.to_owned()),
        text: Set(form.text.to_owned()),
        ..Default::default()
    }
    .save(&amp;conn)
    .await
    .expect(&quot;could not insert post&quot;);

    Flash::success(Redirect::to(&quot;/&quot;), &quot;Post successfully added.&quot;)
}

#[post(&quot;/&lt;id&gt;&quot;, data = &quot;&lt;post_form&gt;&quot;)]
async fn update(conn: Connection&lt;Db&gt;, id: i32, post_form: Form&lt;post::Model&gt;) -&gt; Flash&lt;Redirect&gt; {
    // 注意： find_by_id 关联函数
    let post: post::ActiveModel = Post::find_by_id(id)
        .one(&amp;conn)
        .await
        .unwrap()
        .unwrap()
        .into();

    let form = post_form.into_inner();

    post::ActiveModel {
        id: post.id,
        title: Set(form.title.to_owned()),
        text: Set(form.text.to_owned()),
    }
    .save(&amp;conn)
    .await
    .expect(&quot;could not edit post&quot;);

    Flash::success(Redirect::to(&quot;/&quot;), &quot;Post successfully edited.&quot;)
}

#[get(&quot;/?&lt;page&gt;&amp;&lt;posts_per_page&gt;&quot;)]
async fn list(
    conn: Connection&lt;Db&gt;,
    posts_per_page: Option&lt;usize&gt;,
    page: Option&lt;usize&gt;,
    flash: Option&lt;FlashMessage&lt;'_&gt;&gt;,
) -&gt; Template {
    // Set page number and items per page
    let page = page.unwrap_or(1);
    let posts_per_page = posts_per_page.unwrap_or(DEFAULT_POSTS_PER_PAGE);
    if page == 0 {
        panic!(&quot;Page number cannot be zero&quot;);
    }

    // Setup paginator
    // 注意： find() 函数
    let paginator = Post::find()
        // 注意 order_by_asc 函数
        .order_by_asc(post::Column::Id)
        .paginate(&amp;conn, posts_per_page);
    let num_pages = paginator.num_pages().await.ok().unwrap();

    // Fetch paginated posts
    let posts = paginator
        .fetch_page(page - 1)
        .await
        .expect(&quot;could not retrieve posts&quot;);

    Template::render(
        &quot;index&quot;,
        context! {
            page: page,
            posts_per_page: posts_per_page,
            posts: posts,
            flash: flash.map(FlashMessage::into_inner),
            num_pages: num_pages,
        },
    )
}

#[get(&quot;/&lt;id&gt;&quot;)]
async fn edit(conn: Connection&lt;Db&gt;, id: i32) -&gt; Template {
    // 注意： post::Model 
    let post: Option&lt;post::Model&gt; = Post::find_by_id(id)
        .one(&amp;conn)
        .await
        .expect(&quot;could not find post&quot;);

    Template::render(
        &quot;edit&quot;,
        context! {
            post: post,
        },
    )
}
<span class="boring">}
</span></code></pre></pre>
<p>上面示例中，我们发现有很多来自于 ActiveRecord 的影子（标注注释的地方）。</p>
<p>如果你没有使用 Rails 和 ActiveRecord 的经验，也没有关系。至少你现在已经对 ActiveRecord 有了一个初步的印象：</p>
<ol>
<li>数据模型 和 数据表 存在一一映射的关系，命名上甚至可能还有默认约定存在。</li>
<li>ORM 会自动生成一些查询方法，比如 <code>find_by_id</code> / <code>find</code> 等等。</li>
</ol>
<p>然后，我们在看看 <code>post.rs</code> 示例：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, FromForm)]
#[serde(crate = &quot;rocket::serde&quot;)]
// 关于表名，和 Diesel 处理类似，你可以自己设置
// 这个 Model 是示例中定义的和数据表 `posts` 对应的数据模型，你也可以命名为 `Post`
#[sea_orm(table_name = &quot;posts&quot;)] 
pub struct Model {
    // 可以通过宏指定主键
    #[sea_orm(primary_key)]
    pub id: i32,
    pub title: String,
    #[sea_orm(column_type = &quot;Text&quot;)]
    pub text: String,
}

// 暂时不清楚这是起什么作用
// 几乎每个示例都会有这个类型，但没有使用它的地方
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

// 为 `ActiveModel` 实现一个 `ActiveModelBehavior ` trait 
// 这里有点莫名其妙 ，`ActiveModel` 和 `ActiveModelBehavior ` 应该都是 sea-orm 内部的
// 暂时猜测这行代码是为 Model 实现了一些默认行为，比如`find_by_id` 之类
impl ActiveModelBehavior for ActiveModel {}
<span class="boring">}
</span></code></pre></pre>
<p>至少，我们通过示例代码，找到了 SeaORM 框架架构的关键信息： <code>ActiveModel</code>/ <code>ActiveModelBehavior</code> / <code>Entity</code> 等。</p>
<p>我们继续找一个更加复杂的例子: <a href="https://link.zhihu.com/?target=https%3A//github.com/SeaQL/sea-orm/tree/master/examples/async-std">examples/async-std</a></p>
<p>在这个例子里描述了如图这样的表关系：</p>
<p><img src="assets/0100.webp" alt="" /></p>
<p>按照 ActiveRecord 的思想，每个表要映射一个数据模型：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// https://github.com/SeaQL/sea-orm/blob/master/examples/async-std/src/example_cake.rs
// example_cake.rs 对应 cake 表

use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = &quot;cake&quot;)]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub name: String,
}

// 现在我们已经明确，Relation 类型是为了定义表关系
// 这里 Fruit 和 Cake 之间存在关系
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
    Fruit,
}

// 这里就是使用 `RelationTrait` 来定义它们之间的关系
impl RelationTrait for Relation {
    fn def(&amp;self) -&gt; RelationDef {
        match self {
            // 通过 `Entity::has_many` 函数来指定 Cake 和 Fruit 的一对多关系
            // Cake has_many Fruit
            // 返回的是 RelationDef 类型
            Self::Fruit =&gt; Entity::has_many(super::fruit::Entity).into(),
        }
    }
}

// 另外一个 trait ： Related
impl Related&lt;super::fruit::Entity&gt; for Entity {
    // 此次应该是返回 Cake 模型有关系的 model 信息
    fn to() -&gt; RelationDef {
        Relation::Fruit.def()
    }
}

// Cake 和 filling 之间是 多对多关系
impl Related&lt;super::filling::Entity&gt; for Entity {
    fn to() -&gt; RelationDef {
        // 多对多关系通过中间表 cake_filling 来指定
        super::cake_filling::Relation::Filling.def()
    }

    fn via() -&gt; Option&lt;RelationDef&gt; {
        // 多对多关系通过中间表 cake_filling 来指定
        // 这里是指 via Cake to filling
        Some(super::cake_filling::Relation::Cake.def().rev())
    }
}

// 熟悉的行为
// 为什么不直接由框架实现？
impl ActiveModelBehavior for ActiveModel {}
<span class="boring">}
</span></code></pre></pre>
<p>再看看 <code>Fruit</code> :</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// https://github.com/SeaQL/sea-orm/blob/master/examples/async-std/src/example_fruit.rs
use sea_orm::entity::prelude::*;

// 注意这个结构体 Entity 
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
pub struct Entity;

// 提供 EntityName trait 来指定 table name
// 根据之前的示例，这里也可以使用宏指定
impl EntityName for Entity {
    fn table_name(&amp;self) -&gt; &amp;str {
        &quot;fruit&quot;
    }
}

#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
pub struct Model {
    pub id: i32,
    pub name: String,
    pub cake_id: Option&lt;i32&gt;,
}

// 这里有一个 DeriveColumn
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
    Id,
    Name,
    CakeId,
}

#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
pub enum PrimaryKey {
    Id,
}

// 实现 PrimaryKeyTrait ，指定 auto_increment 
// 猜测应该也可以通过宏指定
impl PrimaryKeyTrait for PrimaryKey {
    type ValueType = i32;

    fn auto_increment() -&gt; bool {
        true
    }
}

// 设置 Fruit 和 Cake 有关系
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
    Cake,
}

impl ColumnTrait for Column {
    type EntityName = Entity;
  // ColumnType 指定了对应数据库表的类型
    // 猜测框架应该有默认类型映射，这里是出于文档作用来显式指定
    fn def(&amp;self) -&gt; ColumnDef {
        match self {
            Self::Id =&gt; ColumnType::Integer.def(),
            Self::Name =&gt; ColumnType::String(None).def(),
            Self::CakeId =&gt; ColumnType::Integer.def(),
        }
    }
}

impl RelationTrait for Relation {
    fn def(&amp;self) -&gt; RelationDef {
        match self {
            // 指定 和 Cake 的关系，是一对多
            // Fruit belongs_to Cake
            Self::Cake =&gt; Entity::belongs_to(super::cake::Entity)
                .from(Column::CakeId) // 指定外键
                .to(super::cake::Column::Id)
                .into(),
        }
    }
}

impl Related&lt;super::cake::Entity&gt; for Entity {
    // 设置关系
    fn to() -&gt; RelationDef {
        Relation::Cake.def()
    }
}

// 熟悉的操作
impl ActiveModelBehavior for ActiveModel {}
<span class="boring">}
</span></code></pre></pre>
<p>再看 <code>CakeFilling</code> :</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// https://github.com/SeaQL/sea-orm/blob/master/examples/async-std/src/example_cake_filling.rs

use sea_orm::entity::prelude::*;

#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
pub struct Entity;

impl EntityName for Entity {
    fn table_name(&amp;self) -&gt; &amp;str {
        &quot;cake_filling&quot;
    }
}

// Cake 和 Filling 是多对多的关系，所以这个 cake_filling 表是中间表
// 这里需要两个表的外键
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
pub struct Model {
    pub cake_id: i32,
    pub filling_id: i32,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
    CakeId,
    FillingId,
}

#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
pub enum PrimaryKey {
    CakeId,
    FillingId,
}

// 中间表的外键不能自增
impl PrimaryKeyTrait for PrimaryKey {
    type ValueType = (i32, i32);

    fn auto_increment() -&gt; bool {
        false
    }
}

#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
    Cake,
    Filling,
}

impl ColumnTrait for Column {
    type EntityName = Entity;

    fn def(&amp;self) -&gt; ColumnDef {
        match self {
            Self::CakeId =&gt; ColumnType::Integer.def(),
            Self::FillingId =&gt; ColumnType::Integer.def(),
        }
    }
}

impl RelationTrait for Relation {
    fn def(&amp;self) -&gt; RelationDef {
        match self {
            // 设置 多对多关系
            // CakeFilling belongs_to Cake
            Self::Cake =&gt; Entity::belongs_to(super::cake::Entity)
                .from(Column::CakeId)
                .to(super::cake::Column::Id)
                .into(),
      // CakeFilling belongs_to Filling
            Self::Filling =&gt; Entity::belongs_to(super::filling::Entity)
                .from(Column::FillingId)
                .to(super::filling::Column::Id)
                .into(),
        }
    }
}

impl ActiveModelBehavior for ActiveModel {}
<span class="boring">}
</span></code></pre></pre>
<p>接下来，我们可以看看示例代码中关于表操作的代码：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// https://github.com/SeaQL/sea-orm/blob/master/examples/async-std/src/select.rs

// 查询一对多关系的方法
async fn find_together(db: &amp;DbConn) -&gt; Result&lt;(), DbErr&gt; {
    print!(&quot;find cakes and fruits: &quot;);

    // 通过 find_also_related 方法进行一对多关联查询
    let both: Vec&lt;(cake::Model, Option&lt;fruit::Model&gt;)&gt; =
        Cake::find().find_also_related(Fruit).all(db).await?;

    println!();
    for bb in both.iter() {
        println!(&quot;{:?}\n&quot;, bb);
    }

    Ok(())
}


// 查询多对多关系的方法
async fn find_many_to_many(db: &amp;DbConn) -&gt; Result&lt;(), DbErr&gt; {
    print!(&quot;find cakes and fillings: &quot;);

    // 看得出来，通过提供的 `find_with_related` 可以进行关联查询
    let both: Vec&lt;(cake::Model, Vec&lt;filling::Model&gt;)&gt; =
        Cake::find().find_with_related(Filling).all(db).await?;

    println!();
    for bb in both.iter() {
        println!(&quot;{:?}\n&quot;, bb);
    }

    print!(&quot;find fillings for cheese cake: &quot;);

    let cheese = Cake::find_by_id(1).one(db).await?;

    if let Some(cheese) = cheese {
        // find_related
        let fillings: Vec&lt;filling::Model&gt; = cheese.find_related(Filling).all(db).await?;

        println!();
        for ff in fillings.iter() {
            println!(&quot;{:?}\n&quot;, ff);
        }
    }

    print!(&quot;find cakes for lemon: &quot;);

    let lemon = Filling::find_by_id(2).one(db).await?;

    if let Some(lemon) = lemon {
        let cakes: Vec&lt;cake::Model&gt; = lemon.find_related(Cake).all(db).await?;

        println!();
        for cc in cakes.iter() {
            println!(&quot;{:?}\n&quot;, cc);
        }
    }

    Ok(())
}

// from : https://github.com/SeaQL/sea-orm/blob/master/examples/async-std/src/operation.rs

pub async fn insert_and_update(db: &amp;DbConn) -&gt; Result&lt;(), DbErr&gt; {
    let pear = fruit::ActiveModel {
        // 注意 ： Set 是函数
        name: Set(&quot;pear&quot;.to_owned()),
        ..Default::default()
    };
    // insert 函数
    let res = Fruit::insert(pear).exec(db).await?;

    println!();
    println!(&quot;Inserted: last_insert_id = {}\n&quot;, res.last_insert_id);

    let pear: Option&lt;fruit::Model&gt; = Fruit::find_by_id(res.last_insert_id).one(db).await?;

    println!();
    println!(&quot;Pear: {:?}\n&quot;, pear);

    let mut pear: fruit::ActiveModel = pear.unwrap().into();
    pear.name = Set(&quot;Sweet pear&quot;.to_owned());
    // update 函数
    let pear: fruit::ActiveModel = pear.update(db).await?;

    println!();
    println!(&quot;Updated: {:?}\n&quot;, pear);

    Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<p>通过上面一系列从模型定义到数据操作，我们看得出来，SeaORM 的设计确实和 ActiveRecord 类似。如果开发者对 ActiveRecord 熟悉，那么会感觉很容易上手。比如，设置表关系的 DSL 方法： <code>has_many</code> 和 <code>belongs_to</code> 。</p>
<p>当然，SeaORM 也提供一些方便的编写数据迁移功能的方法和函数：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// https://github.com/SeaQL/sea-orm/blob/master/examples/rocket_example/src/setup.rs

use sea_orm::sea_query::{ColumnDef, TableCreateStatement};
use sea_orm::{error::*, sea_query, DbConn, ExecResult};

async fn create_table(db: &amp;DbConn, stmt: &amp;TableCreateStatement) -&gt; Result&lt;ExecResult, DbErr&gt; {
    let builder = db.get_database_backend();
    db.execute(builder.build(stmt)).await
}

pub async fn create_post_table(db: &amp;DbConn) -&gt; Result&lt;ExecResult, DbErr&gt; {
    let stmt = sea_query::Table::create()
        .table(super::post::Entity)
        .if_not_exists()
        .col(
            ColumnDef::new(super::post::Column::Id)
                .integer()
                .not_null()
                .auto_increment()
                .primary_key(),
        )
        .col(
            ColumnDef::new(super::post::Column::Title)
                .string()
                .not_null(),
        )
        .col(
            ColumnDef::new(super::post::Column::Text)
                .string()
                .not_null(),
        )
        .to_owned();

    create_table(db, &amp;stmt).await
}
<span class="boring">}
</span></code></pre></pre>
<p>是通过 <code>sql_query</code> 组件提供的功能，我们接下来就会介绍它。</p>
<p>到目前为止，我们已经基本了解 SeaORM 的 架构设计 和 关键 概念和 API，让我们继续探索 SeaORM 的源码实现。</p>
<h3 id="seaorm-源码架构"><a class="header" href="#seaorm-源码架构"><strong>SeaORM 源码架构</strong></a></h3>
<p>Rails 的 ActiveRecord ORM 是一个功能相当丰富和成熟的框架，并且还细分了很多组件:</p>
<ul>
<li>ActiveModel: 是从 ActiveRecord 抽象出来的组件，它是数据模型的抽象接口。</li>
<li>ActiveRecord: 专注于 数据库相关功能</li>
<li>ActiveStorage: 是 ActiveRecord 抽象的延伸，专门负责抽象和处理文件上传相关。</li>
</ul>
<p>反观 SeaORM ，目前还很单薄，但是反过来看，未来也是充满想象的。</p>
<p>SeaORM 中也提供了 ActiveModel 抽象。</p>
<h3 id="entity-与-activemodel-抽象"><a class="header" href="#entity-与-activemodel-抽象"><strong>Entity 与 ActiveModel 抽象</strong></a></h3>
<h3 id="entity-抽象"><a class="header" href="#entity-抽象"><strong>Entity 抽象</strong></a></h3>
<p>主要代码在 <code>https://github.com/SeaQL/sea-orm/tree/master/src/entity</code> 目录下。</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Entity 必须是有 Entity Name 的，并且要实现 

// 这种写法避免了泛型限定过长
// `Iden` 是在 SeaQuery 中定义的，它表示任意查询语句中的标识符，可以转换为字符串
pub trait IdenStatic: Iden + Copy + Debug + 'static {
    fn as_str(&amp;self) -&gt; &amp;str;
}

// 作为一个 Entity ，应该有特定的行为
pub trait EntityName: IdenStatic + Default {
    fn schema_name(&amp;self) -&gt; Option&lt;&amp;str&gt; {
        None
    }

    fn table_name(&amp;self) -&gt; &amp;str;

    fn module_name(&amp;self) -&gt; &amp;str {
        self.table_name()
    }

    fn table_ref(&amp;self) -&gt; TableRef {
        match self.schema_name() {
            Some(schema) =&gt; (Alias::new(schema).into_iden(), self.into_iden()).into_table_ref(),
            None =&gt; self.into_table_ref(),
        }
    }
}

/// An Entity implementing `EntityTrait` represents a table in a database.
///
/// This trait provides an API for you to inspect it's properties
/// - Column (implemented [`ColumnTrait`])
/// - Relation (implemented [`RelationTrait`])
/// - Primary Key (implemented [`PrimaryKeyTrait`] and [`PrimaryKeyToColumn`])
///
/// This trait also provides an API for CRUD actions
/// - Select: `find`, `find_*`
/// - Insert: `insert`, `insert_*`
/// - Update: `update`, `update_*`
/// - Delete: `delete`, `delete_*`
pub trait EntityTrait: EntityName {
    type Model: ModelTrait&lt;Entity = Self&gt; + FromQueryResult;

    type Column: ColumnTrait;

    type Relation: RelationTrait;

    type PrimaryKey: PrimaryKeyTrait + PrimaryKeyToColumn&lt;Column = Self::Column&gt;;

    fn belongs_to&lt;R&gt;(related: R) -&gt; RelationBuilder&lt;Self, R&gt;
    where
        R: EntityTrait,
    {
        RelationBuilder::new(RelationType::HasOne, Self::default(), related, false)
    }

    fn has_one&lt;R&gt;(_: R) -&gt; RelationBuilder&lt;Self, R&gt;
    where
        R: EntityTrait + Related&lt;Self&gt;,
    {
        RelationBuilder::from_rel(RelationType::HasOne, R::to().rev(), true)
    }

    fn has_many&lt;R&gt;(_: R) -&gt; RelationBuilder&lt;Self, R&gt;
    where
        R: EntityTrait + Related&lt;Self&gt;,
    {
        RelationBuilder::from_rel(RelationType::HasMany, R::to().rev(), true)
    }
  
   fn find() -&gt; Select&lt;Self&gt; {
        Select::new()
    }
  
   fn find_by_id(values: &lt;Self::PrimaryKey as PrimaryKeyTrait&gt;::ValueType) -&gt; Select&lt;Self&gt; {
        let mut select = Self::find();
        let mut keys = Self::PrimaryKey::iter();
        for v in values.into_value_tuple() {
            if let Some(key) = keys.next() {
                let col = key.into_column();
                select = select.filter(col.eq(v));
            } else {
                panic!(&quot;primary key arity mismatch&quot;);
            }
        }
        if keys.next().is_some() {
            panic!(&quot;primary key arity mismatch&quot;);
        }
        select
    }
   
   fn insert&lt;A&gt;(model: A) -&gt; Insert&lt;A&gt;
    where
        A: ActiveModelTrait&lt;Entity = Self&gt;,
    {
        Insert::one(model)
    }
  
   fn insert_many&lt;A, I&gt;(models: I) -&gt; Insert&lt;A&gt;
    where
        A: ActiveModelTrait&lt;Entity = Self&gt;,
        I: IntoIterator&lt;Item = A&gt;,
    {
        Insert::many(models)
    }
  
   fn update&lt;A&gt;(model: A) -&gt; UpdateOne&lt;A&gt;
    where
        A: ActiveModelTrait&lt;Entity = Self&gt;,
    {
        Update::one(model)
    }

   
   fn update_many() -&gt; UpdateMany&lt;Self&gt; {
        Update::many(Self::default())
    }
  
   fn delete&lt;A&gt;(model: A) -&gt; DeleteOne&lt;A&gt;
    where
        A: ActiveModelTrait&lt;Entity = Self&gt;,
    {
        Delete::one(model)
    }
  
   fn delete_many() -&gt; DeleteMany&lt;Self&gt; {
        Delete::many(Self::default())
    }
}
<span class="boring">}
</span></code></pre></pre>
<p>通过上面的关键代码，看得出来，一个 Entity 满足下面条件：</p>
<ol>
<li>必须实现 <code>EntityTrait</code></li>
<li>有 <code>Model</code>/ <code>Clomen</code>/ <code>Relation</code>/ <code>PrimaryKey</code> 四个关联类型</li>
<li>提供一些默认行为，包括：<code>belongs_to</code>/ <code>has_many</code>/ <code>CRUD</code>相关方法</li>
</ol>
<p>再看 <code>ModelTrait</code> :</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// https://github.com/SeaQL/sea-orm/blob/master/src/entity/model.rs
pub trait ModelTrait: Clone + Send + Debug {
    type Entity: EntityTrait;

    fn get(&amp;self, c: &lt;Self::Entity as EntityTrait&gt;::Column) -&gt; Value;

    fn set(&amp;mut self, c: &lt;Self::Entity as EntityTrait&gt;::Column, v: Value);

    // 内连接（inner join）
    // Select 结构体对应查询对象
    fn find_related&lt;R&gt;(&amp;self, _: R) -&gt; Select&lt;R&gt;
    where
        R: EntityTrait,
        Self::Entity: Related&lt;R&gt;,
    {
        &lt;Self::Entity as Related&lt;R&gt;&gt;::find_related().belongs_to(self)
    }

  // 内连接（inner join），方向与 find_related 相反
    fn find_linked&lt;L&gt;(&amp;self, l: L) -&gt; Select&lt;L::ToEntity&gt;
    where
        L: Linked&lt;FromEntity = Self::Entity&gt;,
    {
        let tbl_alias = &amp;format!(&quot;r{}&quot;, l.link().len() - 1);
        l.find_linked().belongs_to_tbl_alias(self, tbl_alias)
    }
}
<span class="boring">}
</span></code></pre></pre>
<p>如果说 Entity 是对 数据库中表的映射，那么 Model 是对 Entity 行为的抽象。</p>
<p><code>ModelTrait</code> 中定义了 一个 Model 应该可以 Get/Set 一个字段的值（Value），并且可以通过 <code>find_related</code> 方法可以查询 <code>belongs_to</code> 关系。</p>
<h3 id="activemodel-抽象"><a class="header" href="#activemodel-抽象"><strong>ActiveModel 抽象</strong></a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// https://github.com/SeaQL/sea-orm/blob/master/src/entity/active_model.rs

// ActiveRecord 模式中，Entity 对应每张表，那么表中每一行数据就代表一个 Active 对象
// ActiveValue 代表「当前活动行」Value
#[derive(Clone, Debug, Default)]
pub struct ActiveValue&lt;V&gt;
where
    V: Into&lt;Value&gt;,
{
    value: Option&lt;V&gt;,
    state: ActiveValueState,
}

// 这里刻意使用 驼峰式 来定义这个方法，个人理解是为了突出表达语义
#[allow(non_snake_case)]
pub fn Set&lt;V&gt;(v: V) -&gt; ActiveValue&lt;V&gt;
where
    V: Into&lt;Value&gt;,
{
    ActiveValue::set(v)
}

// ActiveValue 状态
#[derive(Clone, Debug)]
enum ActiveValueState {
    Set,
    Unchanged,
    Unset,
}

// ActiveModelTrait trait 定义了 ActiveModel 的行为
#[async_trait]
pub trait ActiveModelTrait: Clone + Debug {
    type Entity: EntityTrait;

    fn take(&amp;mut self, c: &lt;Self::Entity as EntityTrait&gt;::Column) -&gt; ActiveValue&lt;Value&gt;;

    fn get(&amp;self, c: &lt;Self::Entity as EntityTrait&gt;::Column) -&gt; ActiveValue&lt;Value&gt;;

    fn set(&amp;mut self, c: &lt;Self::Entity as EntityTrait&gt;::Column, v: Value);

    fn unset(&amp;mut self, c: &lt;Self::Entity as EntityTrait&gt;::Column);

    fn is_unset(&amp;self, c: &lt;Self::Entity as EntityTrait&gt;::Column) -&gt; bool;

    fn default() -&gt; Self;

    async fn insert(self, db: &amp;DatabaseConnection) -&gt; Result&lt;Self, DbErr&gt;
    where
        &lt;Self::Entity as EntityTrait&gt;::Model: IntoActiveModel&lt;Self&gt;,
    {
        let am = self;
        let exec = &lt;Self::Entity as EntityTrait&gt;::insert(am).exec(db);
        let res = exec.await?;
        // Assume valid last_insert_id is not equals to Default::default()
        if res.last_insert_id
            != &lt;&lt;Self::Entity as EntityTrait&gt;::PrimaryKey as PrimaryKeyTrait&gt;::ValueType::default()
        {
            let found = &lt;Self::Entity as EntityTrait&gt;::find_by_id(res.last_insert_id)
                .one(db)
                .await?;
            match found {
                Some(model) =&gt; Ok(model.into_active_model()),
                None =&gt; Err(DbErr::Exec(&quot;Failed to find inserted item&quot;.to_owned())),
            }
        } else {
            Ok(Self::default())
        }
    }

    async fn update(self, db: &amp;DatabaseConnection) -&gt; Result&lt;Self, DbErr&gt; {
        let exec = Self::Entity::update(self).exec(db);
        exec.await
    }

    /// Insert the model if primary key is unset, update otherwise.
    /// Only works if the entity has auto increment primary key.
    async fn save(self, db: &amp;DatabaseConnection) -&gt; Result&lt;Self, DbErr&gt;
    where
        Self: ActiveModelBehavior,
        &lt;Self::Entity as EntityTrait&gt;::Model: IntoActiveModel&lt;Self&gt;,
    {
        let mut am = self;
        am = ActiveModelBehavior::before_save(am);
        let mut is_update = true;
        for key in &lt;Self::Entity as EntityTrait&gt;::PrimaryKey::iter() {
            let col = key.into_column();
            if am.is_unset(col) {
                is_update = false;
                break;
            }
        }
        if !is_update {
            am = am.insert(db).await?;
        } else {
            am = am.update(db).await?;
        }
        am = ActiveModelBehavior::after_save(am);
        Ok(am)
    }

    /// Delete an active model by its primary key
    async fn delete(self, db: &amp;DatabaseConnection) -&gt; Result&lt;DeleteResult, DbErr&gt;
    where
        Self: ActiveModelBehavior,
    {
        let mut am = self;
        am = ActiveModelBehavior::before_delete(am);
        let exec = Self::Entity::delete(am).exec(db);
        exec.await
    }
}

// ActiveModelBehavior 中定义用户可以自定义的行为
/// Behaviors for users to override
pub trait ActiveModelBehavior: ActiveModelTrait {
    /// Create a new ActiveModel with default values. Also used by `Default::default()`.
    fn new() -&gt; Self {
        &lt;Self as ActiveModelTrait&gt;::default()
    }

    /// Will be called before saving
    fn before_save(self) -&gt; Self {
        self
    }

    /// Will be called after saving
    fn after_save(self) -&gt; Self {
        self
    }

    /// Will be called before deleting
    fn before_delete(self) -&gt; Self {
        self
    }
}
<span class="boring">}
</span></code></pre></pre>
<p>ActiveModel 代表的就是 活动中的 数据模型，对应当前被操作的表数据。</p>
<p>Rails 中的 ActiveModel 还提供一些模型验证等丰富的功能，目前 SeaORM 中的 ActiveModel 抽象也正在完善相关功能，参见 <a href="https://zhuanlan.zhihu.com/p/417431284/https://github.com/SeaQL/sea-orm/pull/210">PR: Update ActiveModelBehavior API #210</a> 。</p>
<p><strong>Entity 和 ActiveModel 抽象是 SeaORM 抽象架构的基石。</strong></p>
<h3 id="dsl-宏与代码生成"><a class="header" href="#dsl-宏与代码生成"><strong>DSL: 宏与代码生成</strong></a></h3>
<p>我们通过前面的示例看到 SeaORM 提供了一些 DSL 方法。除此之外，SeaORM 还提供了一些代码生成和宏，来方便开发。</p>
<p><a href="https://link.zhihu.com/?target=https%3A//github.com/SeaQL/sea-orm/tree/master/sea-orm-cli">sea-orm-cli</a></p>
<p>为 <code>cargo run</code> 提供了命令参数 <code>-- generate entity</code> 根据数据库表自动生成 <code>Entity</code> 文件。</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span><span class="boring">MySQL (`--database-schema` option is ignored) 
</span>cargo run -- generate entity -u mysql://sea:sea@localhost/bakery -o out

<span class="boring">PostgreSQL
</span>cargo run -- generate entity -u postgres://sea:sea@localhost/bakery -s public -o out
<span class="boring">}
</span></code></pre></pre>
<p>内部是通过 <code>sea-orm-codegen 组件提供的 transform 来生成的 Entity 文件</code> 。</p>
<pre><code>sea-orm-macros
</code></pre>
<p>在 <code>sea-orm-macros</code> 组件中，实现了 <code>DeriveEntity</code>/ <code>DeriveColumn</code>/ <code>DerivePrimaryKey</code> /<code>DeriveModel</code>/<code>DeriveActiveModel</code>/ <code>DeriveActiveModelBehavior</code>等过程宏。</p>
<p>你可以通过 <code>cargo run -- generate entity</code> 来自动生成 Entity 文件，也可以通过这些过程宏自定义 Entity 文件。</p>
<h3 id="多数据库支持"><a class="header" href="#多数据库支持"><strong>多数据库支持</strong></a></h3>
<p>SeaORM 的 <code>src</code> 目录下还有关于 <code>database</code>/ <code>driver</code>/ <code>query</code>/ <code>executor</code> 等模块，主要负责底层数据库交互了，这些功能主要基于 <code>sqlx</code> 和 <code>SeaQuery</code> 构建。</p>
<h3 id="seaquery"><a class="header" href="#seaquery"><strong>SeaQuery</strong></a></h3>
<p>SeaQuery是一个查询生成器，是 SeaORM的基础，用来在Rust中构建动态SQL查询，使用一个符合人体工程学的 API 将表达式、查询和模式构建为抽象语法树（AST）。在同一个 接口 后面，统一支持MySQL、Postgres和SQLite。它类似于 Rails 的 ActiveRecord ORM 框架的 <a href="https://link.zhihu.com/?target=https%3A//github.com/rails/rails/tree/main/activerecord/lib/arel">Arel</a> 组件。</p>
<p>示例代码：</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// 参数绑定
assert_eq!(
    Query::select()
        .column(Glyph::Image)
        .from(Glyph::Table)
        .and_where(Expr::col(Glyph::Image).like(&quot;A&quot;))
        .and_where(Expr::col(Glyph::Id).is_in(vec![1, 2, 3]))
        .build(PostgresQueryBuilder),
    (
        r#&quot;SELECT &quot;image&quot; FROM &quot;glyph&quot; WHERE &quot;image&quot; LIKE $1 AND &quot;id&quot; IN ($2, $3, $4)&quot;#
            .to_owned(),
        Values(vec![
            Value::String(Some(Box::new(&quot;A&quot;.to_owned()))),
            Value::Int(Some(1)),
            Value::Int(Some(2)),
            Value::Int(Some(3))
        ])
    )
);

// 动态查询
Query::select()
    .column(Char::Character)
    .from(Char::Table)
    .conditions(
        // some runtime condition
        true,
        // if condition is true then add the following condition
        |q| {
            q.and_where(Expr::col(Char::Id).eq(1));
        },
        // otherwise leave it as is
        |q| {},
    );

// 生成查询 SQL 
let query = Query::select()
    .column(Char::Character)
    .column((Font::Table, Font::Name))
    .from(Char::Table)
    .left_join(Font::Table, Expr::tbl(Char::Table, Char::FontId).equals(Font::Table, Font::Id))
    .and_where(Expr::col(Char::SizeW).is_in(vec![3, 4]))
    .and_where(Expr::col(Char::Character).like(&quot;A%&quot;))
    .to_owned();

assert_eq!(
    query.to_string(MysqlQueryBuilder),
    r#&quot;SELECT `character`, `font`.`name` FROM `character` LEFT JOIN `font` ON `character`.`font_id` = `font`.`id` WHERE `size_w` IN (3, 4) AND `character` LIKE 'A%'&quot;#
);
assert_eq!(
    query.to_string(PostgresQueryBuilder),
    r#&quot;SELECT &quot;character&quot;, &quot;font&quot;.&quot;name&quot; FROM &quot;character&quot; LEFT JOIN &quot;font&quot; ON &quot;character&quot;.&quot;font_id&quot; = &quot;font&quot;.&quot;id&quot; WHERE &quot;size_w&quot; IN (3, 4) AND &quot;character&quot; LIKE 'A%'&quot;#
);
assert_eq!(
    query.to_string(SqliteQueryBuilder),
    r#&quot;SELECT `character`, `font`.`name` FROM `character` LEFT JOIN `font` ON `character`.`font_id` = `font`.`id` WHERE `size_w` IN (3, 4) AND `character` LIKE 'A%'&quot;#
);
<span class="boring">}
</span></code></pre></pre>
<h2 id="小结"><a class="header" href="#小结"><strong>小结</strong></a></h2>
<p>SeaORM 目前只是 0.2 版本，对比 Rails 的 ActiveRecord 来看，SeaORM 还有很长的路要走。通过这篇文章，我们大概对 SeaORM 有了高屋建瓴的理解，为使用 SeaORM 或 给 SeaORM 做贡献打一个基础。</p>
<p>编辑于 2021-10-06 13:17</p>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                            <a rel="prev" href="../../11_生态观察/Rust_web_frameworks_have_subpar_error_reporting/Rust_web_frameworks_have_subpar_error_reporting.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                                <i class="fa fa-angle-left"></i>
                            </a>

                            <a rel="next" href="../../empty.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                                <i class="fa fa-angle-right"></i>
                            </a>

                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
                    <a rel="prev" href="../../11_生态观察/Rust_web_frameworks_have_subpar_error_reporting/Rust_web_frameworks_have_subpar_error_reporting.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                        <i class="fa fa-angle-left"></i>
                    </a>

                    <a rel="next" href="../../empty.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                        <i class="fa fa-angle-right"></i>
                    </a>
            </nav>

        </div>




        <script type="text/javascript">
            window.playground_copyable = true;
        </script>


        <script src="../../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../mark.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../searcher.js" type="text/javascript" charset="utf-8"></script>

        <script src="../../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../highlight.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../book.js" type="text/javascript" charset="utf-8"></script>

        <!-- Custom JS scripts -->


    </body>
</html>
